package main

import (
	"net/http"
	"os/exec"
	"io"
	"io/ioutil"
	"strings"
	"bytes"
	"os"
	"flag"
	"fmt"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
	log "github.com/labstack/gommon/log"
	"gopkg.in/yaml.v2"
	"github.com/jamlee/scontainer/log/gelf"
	"encoding/json"
)

const METADATA  = "metadata.yml"
const NOUTILUS_URL = "http://%s/v1/script/%s/%s/getaddr"

// 描述信息
type Metadata struct {
	Name string `yaml:"name" json:"name"`
	Command string `yaml:"command" json:"command"`
	Description string `yaml:"description" json:"description"`
	Readme string `yaml:"readme" json:"readme"`
	Dependencies string `yaml:"dependencies" json:"dependencies"`
}

type Config struct {
	Debug bool
	LogDriver string
	Port string
	NoutilusHost string
}

type GetAddr struct {
	Data map[string]string `json:"_data"`
}

func getConfigFromArgs() Config {
	config := Config{}
	flag.StringVar(&config.Port, "port","80","监听端口")
	flag.StringVar(&config.LogDriver, "log-driver","","日志驱动：app.log (本地)或者 gelf://192.192.2.2 （远程）")
	flag.StringVar(&config.NoutilusHost, "noutilus-url","nautilus.dtwh.dev:80","noutilus 的url地址")
	flag.BoolVar(&config.Debug, "debug",false,"是否打开调试模式")
	flag.Parse()
	return config
}

// 统一日志的写入格式
func setUpLogger(e *echo.Echo, config Config, mdYml *Metadata, logger *log.Logger) {
	// 设置默认输出日志格式
	loggerConfig := middleware.LoggerConfig{}
	if config.Debug {
		fmt.Println("-| 开启DEBUG日志模式")
		e.Logger.SetLevel(log.DEBUG)
	}

	logDriver := strings.SplitN(config.LogDriver, "://", 2)
	if len(logDriver) == 1 && logDriver[0] != "" {
		fmt.Println("-| 选择本地文件存储日志:", logDriver[0])
		var file *os.File
		logPath := logDriver[0]
		if _, err := os.Stat(logPath); os.IsNotExist(err) {
			file, _ = os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE, 0644)
		} else {
			file, _ = os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND, 0644)
		}
		defer file.Close()
		// 本地日志使用非json格式，较为容易阅读
		loggerConfig.Format = "method=${method}, uri=${uri}, status=${status}, client_ip=${remote_ip}\n"
		loggerConfig.Output = io.MultiWriter(os.Stdout, file)
		e.Use(middleware.LoggerWithConfig(loggerConfig))
		logger.SetOutput(io.MultiWriter(os.Stdout, file))
	} else if len(logDriver) == 2 && logDriver[0] == "gelf" {
		fmt.Println("-| 选择graylog存储日志:", logDriver[1])
		gelfWriter, err := gelf.NewWriter(logDriver[1], mdYml.Name)
		if err != nil {
			fmt.Fprintln(os.Stderr, "无法链接到graylog驱动")
			os.Exit(1)
		}
		loggerConfig.Output = gelfWriter
		e.Use(middleware.LoggerWithConfig(loggerConfig))
		logger.SetOutput(gelfWriter)
	} else {
		fmt.Println("-| 直接输出日志到 console")
		loggerConfig.Format = "method=${method}, uri=${uri}, status=${status}, client_ip=${remote_ip}\n"
		e.Use(middleware.LoggerWithConfig(loggerConfig))
	}
}

// 验证格式为yaml的元配置文件
func parseYaml() Metadata {
	mdYaml := Metadata{}
	md, err := ioutil.ReadFile(METADATA)
	if err != nil {
		fmt.Println("当前目录下找不到metadata.yml文件")
		os.Exit(1)
	}
	err = yaml.Unmarshal(md, &mdYaml)
	if err != nil {
		fmt.Println("yaml的格式有误，无法进行解析！")
		os.Exit(1)
	}

	if mdYaml.Command == "" {
		fmt.Println("command 必须要被指定")
		os.Exit(1)
	}
	return mdYaml
}

func checkDependencies(deps string, host string, env *map[string]string) {

	// 检查该函数的依赖项是否满足
	dependencies := strings.Split(deps, ",")
	for i := 0; i < len(dependencies); i++ {
		dependency := dependencies[i]
		dependency = strings.Trim(dependency, " ")
		dependencyArr := strings.Split(dependency, ":")
		url := fmt.Sprintf(NOUTILUS_URL, host, dependencyArr[0], dependencyArr[1])
		resp, err := http.Get(url)

		if err != nil {
			fmt.Printf("%s:%s 依赖没有被满足 \n", dependencyArr[0], dependencyArr[1])
			fmt.Printf("err: %v", err)
			os.Exit(1)
		}

		data, err := ioutil.ReadAll(resp.Body)
		res := &GetAddr{}
		json.Unmarshal(data, res)
		addr := res.Data["addr"]

		fmt.Printf("-| get the %s:%s addr %s \n", dependencyArr[0], dependencyArr[1], addr)
		resp, err  = http.Get("http://" + addr)
		if err != nil {
			fmt.Printf("%v \n", err)
			fmt.Printf("the %s:%s addr %s not avaliable \n", dependencyArr[0], dependencyArr[1], addr)
			os.Exit(1)
		} else {
			(*env)[dependency] = addr
		}
	}
}

func setEnv(m map[string]string, env *[]string) {
	fmt.Println("-| set env for command")
	var envStr string
	for k, v := range m {
		envStr = fmt.Sprintf("%s=%s", k, v)
		*env = append(*env, envStr)
		fmt.Printf("-| %s=%s\n", k, v)
	}
}

func main() {
	e := echo.New()
	config := getConfigFromArgs()

	mdYaml := parseYaml()
	logger := log.New(mdYaml.Name)
	setUpLogger(e, config, &mdYaml, logger)

	// 检查依赖注册依赖
	env := make(map[string]string, 100)
	checkDependencies(mdYaml.Dependencies, config.NoutilusHost, &env)
	envArr := make([]string, 100)
	setEnv(env, &envArr)

	commandArr := strings.Split(mdYaml.Command, " ")

	fmt.Println("\n")

	// 开始注册路由
	e.GET("/", func(c echo.Context) error {
		return c.JSON(http.StatusOK, mdYaml)
	})

	e.POST("/", func(c echo.Context) error {
		logger.Info("-| 执行命令:", commandArr[0] + " " + strings.Join(commandArr[1:], " "))

		command := exec.Command(commandArr[0], commandArr[1:]...)
		command.Env = append(os.Environ(), envArr...)

		// 获取Stdin，写入Json字符
		stdin, err := command.StdinPipe()
		if err != nil {
			return c.String(http.StatusServiceUnavailable, "服务端无法获取stdin")
		}

		go func() {
			// 转发 Json 到脚本程序
			defer stdin.Close()
			_, err := io.Copy(stdin, c.Request().Body)
			if err != nil {
				e.Logger.Fatal(err)
			}
		}()

		// 添加错误处理
		var stderr bytes.Buffer
		var stdout bytes.Buffer
		command.Stderr = &stderr
		command.Stdout = &stdout
		// 完全等待stdout输出完毕之后，开始执行
		err = command.Run()
		if err != nil {
			logger.Errorf("遭遇到错误: %s", stderr.String())
			return c.String(http.StatusServiceUnavailable, "调用脚本失败, 请检查输入的参数是否有误")
		} else {
			return c.String(http.StatusOK, string(stdout.String()))
		}
	})

	e.Logger.Fatal(e.Start(":" + config.Port))
}